package com.hnichr.ump.common.service.impl;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.beanutils.BeanMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;

import com.alibaba.fastjson.JSONArray;
import com.hnichr.ump.common.service.AttachParseService;
import com.mxpioframework.common.CommonConstant;
import com.mxpioframework.common.util.BeanReflectionUtils;
import com.mxpioframework.common.vo.Result;
import com.mxpioframework.filestorage.entity.MxpioFileInfo;
import com.mxpioframework.filestorage.service.FileStorageService;
import com.mxpioframework.jpa.annotation.DictAble;
import com.mxpioframework.security.annotation.Attach;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class AttachParseServiceImpl implements AttachParseService {

	private FileStorageService fileStorageService;
	final private Map<Class<?>, Map<String, Attach>> dictListOfClassMap = new ConcurrentHashMap<>();
	private static final Map<Class<?>, Map<String, Field>> declaredFieldsCache = new ConcurrentReferenceHashMap<>(256);
	@Value("${minio.resUrl}")
	private String restUrl;

	@Autowired
	public void setDictService(FileStorageService fileStorageService) {
		this.fileStorageService = fileStorageService;
	}

	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public void parseAttachResult(Result result) {
		Object resultContent = result.getResult();
		if (resultContent == null) {
			return;
		}

		if (resultContent instanceof Page) {
			Page page = (Page) resultContent;
			parseDictTextCollection(page.getContent());
		} else {
			try {
				parseDictPojo(resultContent);
			} catch (Exception e) {
				log.error("parseDictPojo", e);
			}
		}
	}

	@SuppressWarnings("unchecked")
	private <T extends Annotation> T getAnnotationOfField(Class<?> clazz, String property, Class<T> annotationClass) {
		Field field;
		Annotation annotation = null;
		Class<?> currCls = clazz;
		while (!currCls.equals(Object.class) && annotation == null) {
			try {
				field = getDeclaredFields(currCls, property);
				if (field == null) {
					currCls = currCls.getSuperclass();
					continue;
				}
				annotation = field.getAnnotation(annotationClass);
				if (annotation == null) {
					return null;
				}
			} catch (SecurityException e) {
				log.error("获取属性注解失败:" + clazz, e);
			}
		}
		return (T) annotation;
	}

	private Map<String, Attach> loadAttachInfo(Class<?> clazz) {
		Map<String, Attach> dictInfoMap = new HashMap<>();
		BeanInfo beanInfo;
		try {
			beanInfo = Introspector.getBeanInfo(clazz, Object.class);
		} catch (IntrospectionException e) {
			log.error("introspection error", e);
			return dictInfoMap;
		}

		PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
		for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {

			Method readMethod = propertyDescriptor.getReadMethod();
			String propertyName = propertyDescriptor.getName();

			if ("textMap".equals(propertyName)) {
				continue;
			}

			if (readMethod == null) {
				continue;
			}

			Attach attach = readMethod.getAnnotation(Attach.class);
			if (attach == null) {
				attach = getAnnotationOfField(clazz, propertyName, Attach.class);
			}

			if (attach == null) {
				continue;
			}
			dictInfoMap.put(propertyName, attach);
		}
		return dictInfoMap;
	}

	Map<String, Attach> getAttachMapping(Class<?> clazz) {
		Map<String, Attach> dictInfos = dictListOfClassMap.get(clazz);
		if (dictInfos == null) {
			synchronized (this) {
				dictInfos = dictListOfClassMap.get(clazz);
				if (dictInfos == null) {
					dictInfos = loadAttachInfo(clazz);
					dictListOfClassMap.put(clazz, dictInfos);
				}
			}
		}
		return dictInfos;

	}

	@SuppressWarnings("unchecked")
	private void parseDictPojo(Object pojo) {

		if (pojo instanceof Collection) {
			parseDictTextCollection((Collection<Object>) pojo);
			return;
		} else if (pojo.getClass().getName().startsWith("[L")) {
			// object array
			parseDictTextArray((Object[]) pojo);
			return;
		}

		Class<?> clazz = BeanReflectionUtils.getClass(pojo);

		BeanMap itemBeanMap = new BeanMap(pojo);
		for (Map.Entry<String, Attach> entry : getAttachMapping(clazz).entrySet()) {
			try {
				convertProperty(itemBeanMap, pojo, entry);
			} catch (Exception e) {
				log.error("error when convert property.", e);
			}
		}
	}

	private void convertProperty(BeanMap itemBeanMap, Object pojo, Map.Entry<String, Attach> entry) {
		String property = entry.getKey();
		Object value = itemBeanMap.get(property);
		if (value == null) {
			return;
		}
		List<String> attachList = StrUtil.split(Objects.toString(value), StrUtil.COMMA);
		JSONArray array = new JSONArray();
		MxpioFileInfo fileInfo = null;
		for (String fileNo : attachList) {
			try {
				fileInfo = fileStorageService.get(fileNo);
				fileInfo.setAbsolutePath(restUrl + "/" + fileInfo.getAbsolutePath());
				array.add(fileInfo);
			} catch (Exception e) {
			}
		}

		log.debug(" 附件下载 : " + array);

		if (pojo instanceof DictAble) {
			((DictAble) pojo).putText(property + CommonConstant.DICT_TEXT_SUFFIX, array.toJSONString());
		}

	}

	private void parseDictTextArray(Object[] arr) {
		if (arr == null) {
			return;
		}
		for (Object record : arr) {
			try {
				parseDictPojo(record);
			} catch (Exception e) {
				log.error("parseDictPojo", e);
			}
		}
	}

	private void parseDictTextCollection(Collection<Object> collection) {
		if (collection == null) {
			return;
		}
		for (Object record : collection) {
			try {
				parseDictPojo(record);
			} catch (Exception e) {
				log.error("parseDictPojo", e);
			}
		}
	}

	private static Field getDeclaredFields(Class<?> clazz, String name) {
		Assert.notNull(clazz, "Class must not be null");
		Map<String, Field> resultMap = declaredFieldsCache.get(clazz);
		if (resultMap == null) {
			Field[] fields = clazz.getDeclaredFields();
			Map<String, Field> myResultMap = new HashMap<>();
			for (Field field : fields) {
				myResultMap.put(field.getName(), field);
			}
			declaredFieldsCache.put(clazz, myResultMap);
			resultMap = myResultMap;
		}
		return resultMap.get(name);
	}

}
